package 方法句柄;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;

//●方法句柄的概念
//
//方法句柄是一种指向方法的强类型、可执行的引用，它的类型由方法的参数类型和返回值类型组成，而与方法所在类名和方法名无关。
//
//它作为方法的抽象，使方法调用不再受所在类型的约束，能更好的支持动态类型语言特性。同时它也使得函数可以在 java 中成为一等公民，可以作为方法参数进行传递。
//
//看个例子：

/*class Horse {
  public void race() {
    System.out.println("Horse.race()"); 
  }
}

class Deer {
  public void race() {
    System.out.println("Deer.race()");
  }
}

class Cobra {
  public void race() {
    System.out.println("How do you turn this on?");
  }
}*/


//如何用统一的方式调用 race 方法呢？
//
//我们可以用反射，也可以抽象出一个包含 race 方法的接口，但这两种方式都不直接，要么有性能方面的损耗，要么增加了接口约束。
//反射方法：
//public class 方法句柄2 {
//	public static void main(String[] args) throws Exception{
//		Horse h=new Horse();
//		Deer d=new Deer();
//		Cobra c=new Cobra();
//		Method mh = Horse.class.getMethod("race");
//		Method md = Deer.class.getMethod("race");
//		Method mc = Cobra.class.getMethod("race");
//      //public Object invoke(Object obj, Object... args)
//		mh.invoke(h);//Horse.race()
//		md.invoke(d);//Deer.race()
//		mc.invoke(c);//How do you turn this on?
//	}
//}
//class Horse {
//	  public void race() {
//	    System.out.println("Horse.race()"); 
//	  }
//	}
//
//	class Deer {
//	  public void race() {
//	    System.out.println("Deer.race()");
//	  }
//	}
//
//	class Cobra {
//	  public void race() {
//	    System.out.println("How do you turn this on?");
//	  }
//	}
//}
//接口方法:
interface Animal{
	public void race();
}
class Horse implements Animal {
	 public void race() {
	    System.out.println("Horse.race()"); 
	 }
}

class Deer implements Animal{
	 public void race() {
	    System.out.println("Deer.race()");
	 }
}

class Cobra implements Animal{
	 public void race() {
	   System.out.println("How do you turn this on?");
	 }
}
public class 方法句柄2 {
//	public static void animalRace(Animal obj) throws Throwable{
//		obj.race();	
//		}	
//	public static void main(String[] args) throws Throwable{
//		animalRace(new Horse());
//		animalRace(new Deer());
//		animalRace(new Cobra());
//	}
	public static void main(String[] args) throws Throwable{
		Animal animal=new Deer();
		animal.race();
		new Horse().race();
		new Cobra().race();
	}
}

//这个场景使用方法句柄就很合适，将 race 方法抽象成方法句柄，它们的句柄类型一致。这样调用者统一对接 race 方法的句柄，间接调用 race 方法。这其实跟使用接口
//统一交互界面是同样的道理，只不过方法句柄通过 MethodType 对象定义了方法的类型，不需要我们额外自定义类型。

//方法句柄方式的参考实现：

//public class RaceMethodHandleDemo {
/*public class 方法句柄2 {

    public static void startRace(Object obj) throws Throwable {
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        //创建实例方法MethodHandle
        //public static MethodType methodType(Class<?> rtype, Class<?>[] ptypes)  rtype：方法的返回类型，ptypes：方法的入参
        //public MethodHandle findVirtual(Class<?> refc, String name, MethodType type)  refc:要检索的类 name:方法名
        MethodHandle mh = lookup.findVirtual(obj.getClass(), "race", MethodType.methodType(void.class));
        //Object invoke(Object... args)
        mh.invoke(obj);
    }

    public static void main(String[] args) throws Throwable {
        startRace(new Horse());
        startRace(new Deer());
        startRace(new Cobra());
    }
}
class Horse {
	public void race() {
		System.out.println("Horse.race()"); 
	}
}

class Deer {
	public void race() {
		System.out.println("Deer.race()");
	}
}

class Cobra {
	public void race() {
		System.out.println("How do you turn this on?");
	}
}*/


//●方法句柄类型示例

//我们先练习几个小案例，一方面加深对句柄类型的理解，同时也体验下句柄的增删改操作。

//import java.lang.invoke.MethodHandle;
//import java.lang.invoke.MethodHandles;
//import java.lang.invoke.MethodType;

//public class MethodHandleTypeDemo {
/*public class 方法句柄2 {

    public static void a(String s) {
        System.out.println("a: " + s);
    }

    public void b(String s) {
        System.out.println("b: " + s);
    }

    public static int c(String s1, String s2) {
        System.out.println("c: arg0: " + s1 + ", arg1:" + s2);
        return 1;
    }

    public static void main(String[] args) throws Throwable {
        MethodHandles.Lookup l = MethodHandles.lookup();
        MethodHandle mh0 = l.findStatic(方法句柄2.class, "a", MethodType.methodType(void.class, String.class));
        System.out.println("mh0's type: " + mh0.type());//mh0's type: (String)void
        mh0.invokeExact("mh0");//a: mh0

        MethodHandle mh1 = l.findVirtual(方法句柄2.class, "b", MethodType.methodType(void.class, String.class));
        System.out.println("mh1's type: " + mh1.type());//mh1's type: (方法句柄2,String)void
        mh1.invokeExact(new 方法句柄2(), "mh1");//b: mh1

        MethodHandle mh2 = mh1.bindTo(new 方法句柄2());//绑定实例
        System.out.println("mh2's type: " + mh2.type());//mh2's type: (String)void
        mh2.invokeExact("mh2");//b: mh2

        MethodHandle mh3 = l.findStatic(方法句柄2.class, "c", MethodType.methodType(int.class, String.class, String.class));
        System.out.println("mh3's type: " + mh3.type());//mh3's type: (String,String)int
        int c = (int) mh3.invokeExact("mh3-1", "mh3-2");//c: arg0: mh3-1, arg1:mh3-2
        System.out.println("mh3 invokeExact result: " + c);//mh3 invokeExact result: 1

        MethodHandle mh4 = MethodHandles.insertArguments(mh3, 0, "mh4-1");//在参数0位置，绑定参数为"mh4-1"
        System.out.println("mh4's type: " + mh4.type());//mh4's type: (String)int
        int c2 = (int) mh4.invokeExact("mh4-2");//c: arg0: mh4-1, arg1:mh4-2.  虽然只输入一个参数"mh4-2",但参数0已绑定"mh4-1"
        System.out.println("mh4 invokeExact result: " + c2);//mh4 invokeExact result: 1

        MethodHandle mh5 = MethodHandles.dropArguments(mh3, 1, String.class);//解除参数1的绑定
        System.out.println("mh5's type: " + mh5.type());//mh5's type: (String,String,String)int
        int c3 = (int) mh5.invokeExact("mh5-1", "mh5-2", "mh5-3");//c: arg0: mh5-1, arg1:mh5-3  原参数1位置参数"mh5-1"已解除绑定
        System.out.println("mh5 invokeExact result: " + c3);

        MethodHandle mh6 = mh0.asType(MethodType.methodType(void.class, Object.class));//修改参数类型
        System.out.println("mh6's type: " + mh6.type());//mh6's type: (Object)void
        mh6.invokeExact((Object) "mh6");//a: mh6

    }
}*/
/*执行结果如下：

mh0's type: (String)void
a: mh0
mh1's type: (MethodHandleTypeDemo,String)void
b: mh1
mh2's type: (String)void
b: mh2
mh3's type: (String,String)int
c: arg0: mh3-1, arg1:mh3-2
mh3 invokeExact result: 1
mh4's type: (String)int
c: arg0: mh4-1, arg1:mh4-2
mh4 invokeExact result: 1
mh5's type: (String,String,String)int
c: arg0: mh5-1, arg1:mh5-3
mh5 invokeExact result: 1
mh6's type: (Object)void
a: mh6


根据打印结果，我们可以得到以下几点认识：

◾实例方法的第一个参数是实例对象，也需包含在方法句柄类型中（对应 mh1）；

◾静态方法使用 MethodHandles.Lookup 的 findStatic 方法，实例方法使用 MethodHandles.Lookup 的 findVirtual，跟普通的方法调用字节码指令相对
应（invokestatic 和 invokevirtual）；

◾通过 bindTo 绑定实例对象，产生的适配器句柄类型则不包含对象类型了（对比 mh1 和 mh2），这个方法在我们通过 invokedynamic 调用点链接到合法句柄类型时会
使用到。

◾MethodHandles.insertArguments 方法可以在指定位置绑定一个参数，我们可以通过它实现方法的柯里化。比如可以将 f(x, y)方法的 x 参数绑定为 3，生成另
一个方法句柄 g(y) = f(x, y)，每当调用 g(y)时会先在第一个位置插入 3，再调用 f(x, y)的方法句柄。

◾MethodHandles.dropArguments 可以删除指定位置的参数，生成新的方法句柄，原理同 insertArguments。

◾MethodHandle.asType 则可以修改参数类型，为原方法句柄生成适配器。

●方法句柄的权限

方法句柄也有权限问题，但与反射在方法调用时检查不同，它是在句柄创建阶段进行检查的，如果多次调用句柄，它比反射可以省下权限重复检查的开销。

但需注意的是，句柄的访问权限不取决于创建句柄的位置，而是 Lookup 对象的位置。

实例演示：*/

//import java.lang.invoke.MethodHandle;
//import java.lang.invoke.MethodHandles;

//public class MethodHandleAuthorityTest {
/*public class 方法句柄2 {

    public static void main(String[] args) throws Throwable {
        MethodHandles.Lookup l1 = Person.getLookup();
        MethodHandle mh1 = l1.findGetter(Person.class, "name", String.class);
        System.out.println((String) mh1.invokeExact(new Person()));

        MethodHandles.Lookup l2 = MethodHandles.lookup();
        MethodHandle mh2 = l2.findGetter(Person.class, "name1", String.class);
        //System.out.println((String) mh2.invokeExact(new Person()));//直接输入Person实例，出错！

        MethodType mt=MethodType.methodType(void.class);
        System.out.println(mt.toString());//()void
        MethodHandle mh22 = l2.findConstructor(Person.class, mt);
        System.out.println(mh22.toString());//MethodHandle()Person
        System.out.println(mh22.invoke());//方法句柄.Person@8efb846  通过无参构造器构造的Persons实例
        System.out.println(mh2.invoke(mh22.invoke()));//不需要强制转型
        System.out.println((String) mh2.invokeExact((Person)mh22.invokeExact()));//需要强制转型
    }
}

class Person {
    private String name = "Mary";
    public String name1="Name1";
    public static MethodHandles.Lookup getLookup() {
        return MethodHandles.lookup();
    }
}*/

/*结果如下：

Mary
Exception in thread "main" java.lang.IllegalAccessException: member is private: com.mh.Person.name/java.lang.String/getField, from com.mh.MethodHandleAuthorityTest
	at java.lang.invoke.MemberName.makeAccessException(MemberName.java:850)
	at java.lang.invoke.MethodHandles$Lookup.checkAccess(MethodHandles.java:1536)
	at java.lang.invoke.MethodHandles$Lookup.checkField(MethodHandles.java:1484)
	at java.lang.invoke.MethodHandles$Lookup.getDirectFieldCommon(MethodHandles.java:1698)
	at java.lang.invoke.MethodHandles$Lookup.getDirectField(MethodHandles.java:1688)
	at java.lang.invoke.MethodHandles$Lookup.findGetter(MethodHandles.java:1027)
	at com.mh.MethodHandleAuthorityTest.main(MethodHandleAuthorityTest.java:19)

●方法句柄的调用

句柄调用有两种方式：invokeExact 和 invoke。

invokeExact 会严格检查句柄类型跟传入参数类型，如果不一致就会报错。比如句柄类型要求 Object，你传的参数为 String，句柄调用时会报类型不匹配异常。
这个情况下，需要将 String 显示强转成 Object 类型。

两者的区别在于，invokeExact()在调用方法句柄时会试图严格地直接匹配所提供的变量。而invoke()与之不同，在需要的时候，invoke()能够稍微调整一下方法的变
量。invoke()会执行一个asType()转换，它会根据如下的这组规则来进行变量的转换：

◾如果需要的话，原始类型会进行装箱操作
◾如果需要的话，装箱后的原始类型会进行拆箱操作
◾如果必要的话，原始类型会进行扩展
◾void返回类型会转换为0（对于返回原始类型的情况），而对于预期得到引用类型的返回值的地方，将会转换为null
◾null值会被视为正确的，不管静态类型是什么都可以进行传递

这里涉及到一个签名多态性（signature polymorphism）的概念（暂且认为签名等同于方法描述符）。

 public final native @PolymorphicSignature Object invokeExact(Object... args) throws Throwable;

可以看到 invokeExact 方法有一个 @PolymorphicSignature 注解标识。java 虚拟机在处理被该注解标识的方法时，会根据实际参数类型生成方法描述符，
而不是句柄目标方法声明的方法描述符。比如：

 public void test(MethodHandle mh, String s) throws Throwable {
        mh.invokeExact(s);
        mh.invokeExact((Object) s);
 }

	// 该方法对应的字节码
  public void test(java.lang.invoke.MethodHandle, java.lang.String) throws java.lang.Throwable;
    descriptor: (Ljava/lang/invoke/MethodHandle;Ljava/lang/String;)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: aload_1
         1: aload_2
         2: invokevirtual #2                  // Method java/lang/invoke/MethodHandle.invokeExact:(Ljava/lang/String;)V
         5: aload_1
         6: aload_2
         7: invokevirtual #3                  // Method java/lang/invoke/MethodHandle.invokeExact:(Ljava/lang/Object;)V
        10: return


当句柄调用 invokeExact 时会检查 invokevirtual 对应的方法描述符跟句柄的目标方法类型，如果不一致就会报类型不一致异常。

如果要自动适配参数类型，就需要调用 invoke 方法。invoke 方法会通过调用 MethodHandle.asType 方法生成一个句柄的适配器，该适配器会适配参数后再调用
原方法句柄，同时对返回结果也会适配后再返回给用户。当然成功执行的前提是参数和返回结果都能够适配成功，即两个类型能够做类型转换。

●方法句柄的实现

前面提到 jvm 会根据实际参数生成方法描述符，那这个描述符对应的方法是哪个呢？我们来实例验证一下。

在不加-XX:+ShowHiddenFrames 虚拟机参数之前，代码及执行结果如下：

import java.lang.invoke.*;
*/
// -XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames -Djava.lang.invoke.MethodHandle.DUMP_CLASS_FILES=true
//public class Foo {
/*public class 方法句柄2 {
    public static void bar(Object o) {
        new Exception().printStackTrace();
    }

    public static void main(String[] args) throws Throwable {
        MethodHandles.Lookup l = MethodHandles.lookup();
        MethodType t = MethodType.methodType(void.class, Object.class);
        MethodHandle mh = l.findStatic(方法句柄2.class, "bar", t);
        mh.invokeExact(new Object());
    }
}*/
/*
// 输出结果
jjava.lang.Exception
	at 方法句柄.方法句柄2.bar(方法句柄2.java:348)
	at 方法句柄.方法句柄2.main(方法句柄2.java:355)

竟然是直接调用了目标方法？

且慢，在下结论之前，我们需要确保我们拿到的是真实的信息。

显然直接调用目标方法是不行的，因为需要做类型检查，必然需要在调用目标方法之前有所动作。所以显然是虚拟机隐藏了一些信息。

我们可以加上-XX:+ShowHiddenFrames 参数将隐藏的栈帧打印出来，这回结果如下：

java.lang.Exception
	at Foo.bar(Foo.java:13)
	at java.base/java.lang.invoke.DirectMethodHandle$Holder.invokeStatic(DirectMethodHandle$Holder:1000010)
	at java.base/java.lang.invoke.LambdaForm$MH/0x0000000800b42840.invokeExact_MT(LambdaForm$MH:1000019)
	at Foo.main(Foo.java:20)


可以看到 invokeExact 方法调用了一个 LambdaForm 的方法，这是一个共享的、跟方法句柄类型相关的特殊适配器。我们可以
通过-Djava.lang.invoke.MethodHandle.DUMP_CLASS_FILES=true 参数将它导出来：

final class LambdaForm$MH000 {
    @Hidden
    @Compiled
    @ForceInline
    static void invokeExact_MT000_LLL_V(Object var0, Object var1, Object var2) {
        MethodHandle var3;
        Invokers.checkExactType(var3 = (MethodHandle)var0, (MethodType)var2);
        Invokers.checkCustomized(var3);
        var3.invokeBasic(var1);
    }

    static void dummy() {
        String var10000 = "MH.invokeExact_MT000_LLL_V=Lambda(a0:L,a1:L,a2:L)=>{\n    t3:V=Invokers.checkExactType(a0:L,a2:L);\n    t4:V=Invokers.checkCustomized(a0:L);\n    t5:V=MethodHandle.invokeBasic(a0:L,a1:L);void}";
    }
}

// 字节码
static void invokeExact_MT000_LLL_V(java.lang.Object, java.lang.Object, java.lang.Object);
    descriptor: (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
    flags: (0x0008) ACC_STATIC
    Code:
      stack=2, locals=3, args_size=3
         0: aload_0
         1: checkcast     #14                 // class java/lang/invoke/MethodHandle
         4: dup
         5: astore_0
         6: aload_2
         7: checkcast     #16                 // class java/lang/invoke/MethodType
        10: invokestatic  #22                 // Method java/lang/invoke/Invokers.checkExactType:(Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)V
        13: aload_0
        14: invokestatic  #26                 // Method java/lang/invoke/Invokers.checkCustomized:(Ljava/lang/invoke/MethodHandle;)V
        17: aload_0
        18: aload_1
        19: invokevirtual #30                 // Method java/lang/invoke/MethodHandle.invokeBasic:(Ljava/lang/Object;)V
        22: return


可以看到，这个适配器会先调用 Invokers.checkExactType 方法检查参数类型，然后调用 Invokers.checkCustomized 方法。后者会在方法句柄调用多次后进
行优化（对应参数-Djava.lang.invoke.MethodHandle.CUSTOMIZE_THRESHOLD，默认值为 127），最后调用方法句柄的 invokeBasic 方法。

invokeBasic 方法会调用至方法句柄本身持有的适配器中（也是一个 LambdaForm），我们可以通过反射将它打印出来：

Field field = MethodHandle.class.getDeclaredField("form");
field.setAccessible(true);
Object obj = field.get(mh);
System.out.println(obj.toString());


DMH.invokeStatic_L_V=Lambda(a0:L,a1:L)=>{
    t2:L=DirectMethodHandle.internalMemberName(a0:L);
    t3:V=MethodHandle.linkToStatic(a1:L,t2:L);void}


该适配器会访问方法句柄中的 MemberName 对象，将该对象作为参数调用方法句柄的 linkToStatic 方法。该方法会根据 MemberName 对象中的方法地址或方法表索
引跳转至目标方法。

final class MemberName implements Member, Cloneable {
    ...
    //@Injected JVM_Method* vmtarget;
    //@Injected int         vmindex;
    ...


最后再补充说下 Invokers.checkCustomized 方法的作用。因为一开始为方法句柄生成的适配器是共享的，当方法句柄调用多次后，java 虚拟机会为它生成一个特
有的适配器，该适配器将方法句柄当成常量，直接拿它的 MemberName 对象，然后调用 linkToStatic 方法。

我们可以将-Djava.lang.invoke.MethodHandle.CUSTOMIZE_THRESHOLD 设置为 0，此时生成的适配器就是特有的：

final class LambdaForm$DMH000 {
    @Hidden
    @Compiled
    @ForceInline
    static void invokeStatic000_LL_V(Object var0, Object var1) {
        MethodHandle var3 = (MethodHandle)"CONSTANT_PLACEHOLDER_1 <<Foo.bar(Object)void/invokeStatic>>";
        Object var2 = DirectMethodHandle.internalMemberName(var3);
        MethodHandle.linkToStatic(var1, (MemberName)var2);
    }

    static void dummy() {
        String var10000 = "DMH.invokeStatic000_LL_V=Lambda(a0:L,a1:L)=>{\n    t2:L=DirectMethodHandle.internalMemberName(a0:L);\n    t3:V=MethodHandle.linkToStatic(a1:L,t2:L);void}";
    }
}

// 对应字节码
static void invokeStatic000_LL_V(java.lang.Object, java.lang.Object);
    descriptor: (Ljava/lang/Object;Ljava/lang/Object;)V
    flags: (0x0008) ACC_STATIC
    Code:
      stack=2, locals=3, args_size=2
         0: ldc           #14                 // String CONSTANT_PLACEHOLDER_1 <<Foo.bar(Object)void/invokeStatic>>
         2: checkcast     #16                 // class java/lang/invoke/MethodHandle
         5: astore_0
         6: aload_0
         7: invokestatic  #22                 // Method java/lang/invoke/DirectMethodHandle.internalMemberName:(Ljava/lang/Object;)Ljava/lang/Object;
        10: astore_2
        11: aload_1
        12: aload_2
        13: checkcast     #24                 // class java/lang/invoke/MemberName
        16: invokestatic  #28                 // Method java/lang/invoke/MethodHandle.linkToStatic:(Ljava/lang/Object;Ljava/lang/invoke/MemberName;)V
        19: return

方法句柄跟 invokedynamic 指令是什么关系？请听下回分解*/